package com.sgcc.nfc.lock.core.nfc

import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

object AesUtil {

    private const val BLOCK_SIZE = 16
    private const val TRANSFORMATION = "AES/CBC/NoPadding"
    private const val ALGORITHM = "AES"
    private val ZERO_IV = ByteArray(BLOCK_SIZE)

    fun encrypt(data: ByteArray, key: ByteArray, iv: ByteArray = ZERO_IV): ByteArray {
        require(key.size == BLOCK_SIZE) { "Key length must be 16 bytes" }
        require(iv.size == BLOCK_SIZE) { "IV length must be 16 bytes" }
        require(data.size % BLOCK_SIZE == 0) { "Data length must be multiple of 16 bytes" }
        val cipher = Cipher.getInstance(TRANSFORMATION)
        val secretKey = SecretKeySpec(key, ALGORITHM)
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
        return cipher.doFinal(data)
    }

    fun decrypt(data: ByteArray, key: ByteArray, iv: ByteArray = ZERO_IV): ByteArray {
        require(key.size == BLOCK_SIZE) { "Key length must be 16 bytes" }
        require(iv.size == BLOCK_SIZE) { "IV length must be 16 bytes" }
        require(data.size % BLOCK_SIZE == 0) { "Data length must be multiple of 16 bytes" }
        val cipher = Cipher.getInstance(TRANSFORMATION)
        val secretKey = SecretKeySpec(key, ALGORITHM)
        cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
        return cipher.doFinal(data)
    }

    fun encryptUInt(values: List<Int>, fromIndex: Int, toIndex: Int, key: ByteArray, iv: ByteArray = ZERO_IV): List<Int> {
        validateRange(values, fromIndex, toIndex)
        val rangeSize = toIndex - fromIndex + 1
        val plainBytes = toByteArray(values.subList(fromIndex, toIndex + 1))
        val encrypted = encrypt(plainBytes, key, iv)
        val encryptedInts = toIntList(encrypted, rangeSize)
        val result = values.toMutableList()
        for (i in 0 until rangeSize) {
            result[fromIndex + i] = encryptedInts[i]
        }
        return result
    }

    fun decryptUInt(values: List<Int>, fromIndex: Int, toIndex: Int, key: ByteArray, iv: ByteArray = ZERO_IV): List<Int> {
        validateRange(values, fromIndex, toIndex)
        val rangeSize = toIndex - fromIndex + 1
        val encryptedBytes = toByteArray(values.subList(fromIndex, toIndex + 1))
        val decrypted = decrypt(encryptedBytes, key, iv)
        val decryptedInts = toIntList(decrypted, rangeSize)
        val result = values.toMutableList()
        for (i in 0 until rangeSize) {
            result[fromIndex + i] = decryptedInts[i]
        }
        return result
    }

    private fun validateRange(values: List<Int>, fromIndex: Int, toIndex: Int) {
        require(fromIndex in values.indices) { "fromIndex out of bounds" }
        require(toIndex in values.indices) { "toIndex out of bounds" }
        require(fromIndex <= toIndex) { "fromIndex must be <= toIndex" }
        val rangeSize = toIndex - fromIndex + 1
        require(rangeSize * 4 % BLOCK_SIZE == 0) { "Selected range must align to 16-byte blocks" }
    }

    private fun toByteArray(values: List<Int>): ByteArray {
        val buffer = ByteBuffer.allocate(values.size * 4).order(ByteOrder.BIG_ENDIAN)
        values.forEach { buffer.putInt(it) }
        return buffer.array()
    }

    private fun toIntList(bytes: ByteArray, expectedSize: Int): List<Int> {
        val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN)
        val result = mutableListOf<Int>()
        repeat(expectedSize) {
            result.add(buffer.int)
        }
        return result
    }
}
